package mc0101.mccache.api;

import lombok.extern.slf4j.Slf4j;
import mc0101.mccache.param.LockRelativeParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.lettuce.LettuceConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.stereotype.Component;
//import redis.clients.jedis.JedisCluster;

import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class ZcRedisHandleApi {

	private final static String NX = "NX";
	private final static String PX = "PX";
	private final Long SUCCESS = 1L;

//	@Resource
//	private JedisCluster jedisCluster;
	// private static volatile List<JedisCluster> jedisClusters = new
	// ArrayList<>();


	private static final String UNLOCK_LUA;

	/**
	 * 释放锁脚本，原子操作
	 */
	static {
		StringBuilder sb = new StringBuilder();
		sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
		sb.append("then ");
		sb.append("    return redis.call(\"del\",KEYS[1]) ");
		sb.append("else ");
		sb.append("    return 0 ");
		sb.append("end ");
		UNLOCK_LUA = sb.toString();
	}

	@Autowired
	private RedisTemplate<String, String> redisTemplate;

	@Autowired
	private LockRelativeParam lockParamConfig;

//	/**
//	 * 分布式互斥锁-释放锁
//	 */
//	public Object release(String lockKey, String lockerId) {
//		return releaseLock(lockKey, lockerId);
//	}


	/**
	 * 获取分布式锁，原子操作
	 * @param lockKey
	 * @param requestId 唯一ID, 可以使用UUID.randomUUID().toString();
	 * @param expire
	 * @param timeUnit
	 * @return
	 */
	public boolean tryLock(String lockKey, String requestId, long expire, TimeUnit timeUnit) {
		try{
			RedisCallback<Boolean> callback = (connection) -> {
				return connection.set(lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset
						.forName("UTF-8")), Expiration.seconds(timeUnit.toSeconds(expire)), RedisStringCommands
						.SetOption.SET_IF_ABSENT);
			};
			return (Boolean)redisTemplate.execute(callback);
		} catch (Exception e) {
			log.error("redis lock error.{}", e.getMessage());
		}
		return false;
	}

	/**
	 * 释放锁
	 * @param lockKey
	 * @param requestId 唯一ID
	 * @return
	 */
	public boolean releaseLock(String lockKey, String requestId) {
		RedisCallback<Boolean> callback = (connection) -> {
			Object nativeConnection = connection.getNativeConnection();
//			if (nativeConnection instanceof JedisConnection ){
//				return SUCCESS.equals(
//						(Long)((JedisConnection)nativeConnection).eval(UNLOCK_LUA.getBytes(), 1
//						, lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8"))));
//			} else {
				return connection.eval(UNLOCK_LUA.getBytes(), ReturnType.BOOLEAN ,1
						, lockKey.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
//			}
		};
		return (Boolean)redisTemplate.execute(callback);
	}


	/**
	 * 分布式互斥锁-循环加锁
	 */
	public boolean lockUntilPass(String lockKey, String lockerId,
			long expireTimeMilSec, long waitTimeMilSec) throws Exception {
		Long timeOutAt = System.currentTimeMillis() + waitTimeMilSec;
		while (true) {
			if (tryLock(lockKey, lockerId, expireTimeMilSec, TimeUnit.MILLISECONDS)) {
				return true;
			}
			if (timeOutAt < System.currentTimeMillis()) {
				break;
			}
			Thread.sleep(lockParamConfig.retryGetDistributedLockIntevalMillSecond);
		}
		return false;
	}

	/**
	 * 分布式互斥锁-联合加锁: 保证列表内key值都成功加锁且离第一个锁失效留有操作时间
	 *
	 * @param keyList
	 * @param lockerId
	 */
	public boolean lockUnion(List<String> keyList, String lockerId)
			throws Exception {
		lockUntilPass(keyList.get(0), lockerId,
				lockParamConfig.updateQryCountRedisMaxWaitMillSecond,
				lockParamConfig.updateQryCountRedisMaxRetryMillSecond);
		Long timeOutAt = System.currentTimeMillis()
				+ lockParamConfig.updateQryCountRedisMaxWaitMillSecond
				- lockParamConfig.updateQryCountRedisRemainOperationMillSecond;// 余留操作时间
		for (int i = 1; i < keyList.size(); i++) {
			lockUntilPass(keyList.get(i), lockerId,
					lockParamConfig.updateQryCountRedisMaxWaitMillSecond,
					lockParamConfig.updateQryCountRedisMaxRetryMillSecond);
			if (System.currentTimeMillis() > timeOutAt) {
				releaseUnion(keyList, lockerId);// 加锁失败则放掉所有锁
				return false;
			}
		}
		return true;
	}

	/**
	 * 分布式互斥锁-联合解锁：逆序解锁
	 *
	 * @param keyList
	 * @param lockerId
	 */
	public void releaseUnion(List<String> keyList, String lockerId) {
		Collections.reverse(keyList);
		for (String key : keyList) {
			releaseLock(key, lockerId);
		}
	}

	/**
	 * 批量删除
	 *
	 * @param prefix
	 * @param suffix
	 */

//	public void batchDelDay(String prefix, String suffix) {
//		try {
//			// for (JedisCluster jedisCluster : jedisClusters) {
//			// 方法一：一般方法
//			// 先查找所有keys
//			TreeSet<String> keys = new TreeSet<>();
//			Map<String, JedisPool> clusterNodes = jedisCluster
//					.getClusterNodes();
//			for (String k : clusterNodes.keySet()) {
//				log.info("Getting keys from: " + k);
//				JedisPool jp = clusterNodes.get(k);
//				Jedis connection = jp.getResource();
//				try {
//					keys.addAll(connection.keys(prefix + "*" + suffix));
//				} catch (Exception e) {
//					log.error("Getting keys error", e);
//				} finally {
//					log.info("Connection closed.");
//					connection.close();// 用完一定要close这个链接！！！
//				}
//			}
//			log.info("batch del objects: " + keys);
//
//			// 然后删除这些键值对
//			for (String key : keys) {
//				del(key);
//			}
//			// }
//		} catch (Exception e) {
//			log.error("batch delete day keys failed", e);
//		}
//	}

	/**
	 * del
	 */
	public void del(String key) {
		//jedisCluster.del(key);
		redisTemplate.delete(key);
	}

	/**
	 * get
	 */
	public String get(String key) {
//		return jedisCluster.get(key);
		Object value = redisTemplate.opsForValue().get(key);

		if (value != null) {
			String temp;
			try {
				temp = String.valueOf(value);
			} catch (Exception e) {//ssss
				return null;
			}
			return temp;
		}
		return null;
	}

	/**
	 * set
	 */
	public String set(String key, String value) {
		redisTemplate.opsForValue().set(key, value);
		return value;
		//return jedisCluster.set(key, value);
	}

	/**
	 * 自增+1
	 * 
	 * @param key
	 * @return
	 */
	public Long incr(String key) {
		return redisTemplate.opsForValue().increment(key, 1);
	}

	/**
	 * set 有超时时间
	 */
	public String set(String key, String value, int expireSecond) {
		try {
			if (expireSecond > 0) {
				redisTemplate.opsForValue().set(key, value, expireSecond, TimeUnit.SECONDS);
			} else {
				set(key, value);
			}
			return value;
		} catch (Exception e) {//ssss
			return null;
		}
		//return jedisCluster.setex(key, expireSecond, value);
	}

	/**
	 * hashset 是否存在
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	public boolean hexists(String key, String value) {
		//return jedisCluster.hexists(key, value);
		try {
			return redisTemplate.opsForSet().isMember(key, value);
		} catch (Exception e) {//ssss
			return false;
		}
	}

	/**
	 * hashset 增加
	 *
	 * @param key
	 * @param value
	 * @return
	 */
	public Long hset(String key, String... value) {
		//return jedisCluster.hset(key, field, value);
		try {
			return redisTemplate.opsForSet().add(key, value);
		} catch (Exception e) {//ssss
			return 0L;
		}
	}

	/**
	 * hashset 返回指定key的所有field的集合
	 *
	 * @param key
	 * @return
	 */
	public Set<String> hkeys(String key) {
		//return jedisCluster.hkeys(key);
		try {
			return redisTemplate.opsForSet().members(key);
		} catch (Exception e) {//ssss
			return null;
		}
	}

	/**
	 * hashset 删除指定key
	 *
	 * @param key
	 * @param field
	 * @return
	 */
	public Long hdel(String key, String... field) {

		try {
			return redisTemplate.opsForSet().remove(key, field);
		} catch (Exception e) {//ssss
			return 0L;
		}

		//return jedisCluster.hdel(key, field);
	}

	public List<String> getList(String key){
		//return jedisCluster.hvals(key);
		try {
			return redisTemplate.opsForList().range(key, 0, -1);
		} catch (Exception e) {//ssss
			return null;
		}
	}

	public Long addList(String key, String... value){
//		return jedisCluster.lpush(key, value);
		try {
			return redisTemplate.opsForList().rightPushAll(key, value);
		} catch (Exception e) {//ssss
			return 0L;
		}
	}

	/**
	 * 指定缓存失效时间
	 * @param key 键
	 * @param time 时间(秒)
	 * @return
	 */
	public boolean expire(String key,
						  long time) {
		try {
			if (time > 0) {
				redisTemplate.expire(key, time, TimeUnit.SECONDS);
			}
			return true;
		} catch (Exception e) {//ssss
			return false;
		}
	}

}